/*
 * Copyright (c) 2012-2014 Wind River Systems, Inc.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <zephyr/kernel.h>

#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/hci.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/bluetooth/gatt.h>

#include <zephyr/bluetooth/gap.h>

// define connection object
struct bt_conn *my_conn = NULL;
static struct bt_gatt_exchange_params exchange_params;

// create advertising parameters
static struct bt_le_adv_param *adv_param = BT_LE_ADV_PARAM(
	(BT_LE_ADV_OPT_CONNECTABLE |
	 BT_LE_ADV_OPT_USE_IDENTITY), /* Connectable advertising and use identity address */
	800, /* Min Advertising Interval 500ms (800*0.625ms) */
	801, /* Max Advertising Interval 500.625ms (801*0.625ms) */
	NULL); /* Set to NULL for undirected advertising */

// define advertising data
static const struct bt_data ad[] = {
	BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
	BT_DATA(BT_DATA_NAME_COMPLETE, CONFIG_BT_DEVICE_NAME, sizeof(CONFIG_BT_DEVICE_NAME) - 1),
};

// define scan response data
static const struct bt_data sd[] = {
};

static void update_phy(struct bt_conn *conn)
{
	int err;

	// update PHY
	err = bt_conn_le_phy_update(conn, BT_CONN_LE_PHY_PARAM_2M);
	if (err) {
		printk("PHY update failed (err %d)\n", err);
	} else {
		printk("PHY update pending\n");
	}
}

static void update_data_len(struct bt_conn *conn)
{
	int err;

	struct bt_conn_le_data_len_param param =
	{
		.tx_max_len = BT_GAP_DATA_LEN_MAX,
		.tx_max_time = BT_GAP_DATA_TIME_MAX,
	};
	
	// update data length
	err = bt_conn_le_data_len_update(conn, &param);
	if (err) {
		printk("Data length update failed (err %d)\n", err);
	} else {
		printk("Data length update pending\n");
	}
}

static void exchange_func(struct bt_conn *conn, uint8_t att_err,
			  struct bt_gatt_exchange_params *params)
{
	printk("MTU exchange %s\r\n", att_err == 0 ? "successful" : "failed");
	if (!att_err) {
		uint16_t payload_mtu =
			bt_gatt_get_mtu(conn) - 3; // 3 bytes used for Attribute headers.
		printk("New MTU: %d bytes", payload_mtu);
	}
}

static void update_mtu(struct bt_conn *conn)
{
	int err;

	exchange_params.func = exchange_func;

	// update MTU
	err = bt_gatt_exchange_mtu(conn, &exchange_params);
	if (err) {
		printk("MTU exchange failed (err %d)\n", err);
	} else {
		printk("MTU exchange pending\n");
	}
}

void on_connected(struct bt_conn *conn, uint8_t err)
{
	printk("Connected\n");
	my_conn = bt_conn_ref(conn);

	struct bt_conn_info info;
	bt_conn_get_info(conn, &info);

	double connection_interval = info.le.interval * 1.25; // in ms
	uint16_t supervision_timeout = info.le.timeout * 10; // in ms
	printk("Connection parameters: interval %.2f ms, latency %d intervals, timeout %d ms\r\n",
		connection_interval, info.le.latency, supervision_timeout);
	
	update_phy(conn);
	update_data_len(conn);
	update_mtu(conn);

}

void on_disconnected(struct bt_conn *conn, uint8_t reason)
{
    printk("Disconnected (reason %u)\n", reason);
    bt_conn_unref(my_conn);
    my_conn = NULL;

    // Restart advertising
    int err = bt_le_adv_start(adv_param, ad, ARRAY_SIZE(ad), sd, ARRAY_SIZE(sd));
    if (err) {
        printk("Advertising failed to start (Error %d)\n", err);
    } else {
        printk("Advertising successfully restarted\n");
    }
}


void on_le_param_updated(struct bt_conn *conn, uint16_t interval, uint16_t latency,
			 uint16_t timeout)
{
	double connection_interval = interval * 1.25; // in ms
	uint16_t supervision_timeout = timeout * 10; // in ms
	printk("Connection parameters updated: interval %.2f ms, latency %d intervals, timeout %d ms",
		connection_interval, latency, supervision_timeout);
}

void on_le_phy_updated(struct bt_conn *conn, struct bt_conn_le_phy_info *param)
{
	// PHY Updated
	if (param->tx_phy == BT_CONN_LE_TX_POWER_PHY_1M) {
		printk("PHY updated. New PHY: 1M\r\n");
	} else if (param->tx_phy == BT_CONN_LE_TX_POWER_PHY_2M) {
		printk("PHY updated. New PHY: 2M\r\n");
	} else if (param->tx_phy == BT_CONN_LE_TX_POWER_PHY_CODED_S8) {
		printk("PHY updated. New PHY: Long Range\r\n");
	}
}

void on_le_data_len_updated(struct bt_conn *conn, struct bt_conn_le_data_len_info *info)
{
	uint16_t tx_len = info->tx_max_len;
	uint16_t tx_time = info->tx_max_time;
	uint16_t rx_len = info->rx_max_len;
	uint16_t rx_time = info->rx_max_time;
	printk("Data length updated. Length %d/%d bytes, time %d/%d us\r\n", tx_len, rx_len, tx_time,
		rx_time);
}

// define connection callbacks
struct bt_conn_cb connection_callbacks = {
	.connected = on_connected,	
	.disconnected = on_disconnected,
	.le_param_updated = on_le_param_updated,
	.le_phy_updated = on_le_phy_updated,
	.le_data_len_updated = on_le_data_len_updated,
};

int main(void)
{
	int err;
	
	bt_addr_le_t addr;

	// create device address
	bt_addr_le_from_str("FF:EE:DD:CC:BB:AA", "random", &addr);
	bt_id_create(&addr, NULL);

	// register connection callbacks
	bt_conn_cb_register(&connection_callbacks);

	// enable bluetooth
	bt_enable(NULL);

	// start advertising
	bt_le_adv_start(adv_param, ad, ARRAY_SIZE(ad), sd, ARRAY_SIZE(sd));

	printk("This is BLE demo! %s\n", CONFIG_BOARD);
	return 0;
}
